package br.com.puertorico.model.querybuilder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.apache.log4j.Logger;

import br.com.puertorico.model.querybuilder.annotations.Operand;
import br.com.puertorico.model.querybuilder.sorting.SortStrategy;
import br.com.puertorico.model.querybuilder.utils.CriteriaReader;
import br.com.puertorico.model.querybuilder.wrappers.ArgumentWrapper;
import br.com.puertorico.model.querybuilder.wrappers.JoinClauseWrapper;
import br.com.puertorico.model.querybuilder.wrappers.WhereClauseWrapper;
/**
 * Classe respons&aacute;vel por orquestrar o processo de gera&ccedil;&atilde;o
 * de query.
 *
 * @author daniel
 * @version 1.0.0
 */
public class QueryBuilder {

	/** The Constant _log. */
	private static final Logger _log = Logger.getLogger(QueryBuilder.class);

	/**
	 * Builds the query.
	 *
	 * @param em
	 *            the em
	 * @param criteria
	 *            the criteria
	 * @param pageSize
	 *            the page size
	 * @param firstResult
	 *            the first result
	 * @param sortStrategy
	 *            the sort strategy
	 * @return the query
	 */
	public static Query buildQuery(final EntityManager em,
			final Object criteria, int pageSize, int firstResult,
			SortStrategy sortStrategy) {
		CriteriaReader reader = new CriteriaReader(criteria);
		List<ArgumentWrapper> requiredArguments = new ArrayList<ArgumentWrapper>(
				reader.getRequiredArguments());
		List<JoinClauseWrapper> requiredJoinClauses = new ArrayList<JoinClauseWrapper>(
				reader.getRequiredJoinClauses(requiredArguments));
		List<WhereClauseWrapper> requiredWhereClauses = new ArrayList<WhereClauseWrapper>(
				reader.getRequiredWhereClauses(requiredArguments));
		// vamos montar a query
		StringBuilder hql = new StringBuilder();
		hql.append(reader.extractSelectClause()).append(" ");
		hql.append(reader.extractFromClause()).append(" ");
		// vamos inserir os joins
		Collections.sort(requiredJoinClauses);
		for (JoinClauseWrapper join : requiredJoinClauses) {
			hql.append(join.getJoinClause()).append(" ");
		}
		// vamos inserir o trecho do where dos argumentos
		StringBuilder whereHql = new StringBuilder();
		whereHql.append(reader.getArgumentsWhereClause(requiredArguments));
		// vamos inserir os wheres
		Collections.sort(requiredWhereClauses);
		boolean first = !(whereHql.length() > 0);
		for (WhereClauseWrapper where : requiredWhereClauses) {
			if (first) {
				whereHql.append(where.getWhereClause());
			} else {
				whereHql.append(" AND ").append(where.getWhereClause());
			}
		}
		// vamo adiconar a clausula where na hql
		if (whereHql.length() > 0) {
			whereHql.insert(0, "WHERE ");
			hql.append(whereHql);
		}
		// vamos inserir o order by clause
		if (sortStrategy != null) {
			hql.append(sortStrategy.getOrderByClause());
		}
		_log.info(hql);
		Query query = em.createQuery(hql.toString());
		if (pageSize > 0) {
			query.setFirstResult(firstResult);
			query.setMaxResults(pageSize);
		}
		// vamos colocar os parametros
		for (ArgumentWrapper arg : requiredArguments) {
			if (arg.getArgument().operand() == Operand.IS_NOT_NULL
					|| arg.getArgument().operand() == Operand.IS_NULL) {
				continue;
			}
			query.setParameter(arg.getId(), arg.getValue());
		}
		return query;
	}

	/**
	 * Builds the query count.
	 *
	 * @param em
	 *            the em
	 * @param criteria
	 *            the criteria
	 * @return Query para obter numero total de resultados
	 */
	public static Query buildQueryCount(final EntityManager em,
			final Object criteria) {
		CriteriaReader reader = new CriteriaReader(criteria);
		List<ArgumentWrapper> requiredArguments = new ArrayList<ArgumentWrapper>(
				reader.getRequiredArguments());
		List<JoinClauseWrapper> requiredJoinClauses = new ArrayList<JoinClauseWrapper>(
				reader.getRequiredJoinClauses(requiredArguments));
		List<WhereClauseWrapper> requiredWhereClauses = new ArrayList<WhereClauseWrapper>(
				reader.getRequiredWhereClauses(requiredArguments));
		// vamos montar a query
		StringBuilder hql = new StringBuilder();
		hql.append(reader.extractSelectCountClause()).append(" ");
		hql.append(reader.extractFromClause()).append(" ");
		// vamos inserir os joins
		Collections.sort(requiredJoinClauses);
		for (JoinClauseWrapper join : requiredJoinClauses) {
			hql.append(join.getJoinClause()).append(" ");
		}
		// vamos inserir o trecho do where dos argumentos
		StringBuilder whereHql = new StringBuilder();
		whereHql.append(reader.getArgumentsWhereClause(requiredArguments));
		// vamos inserir os wheres
		Collections.sort(requiredWhereClauses);
		boolean first = !(whereHql.length() > 0);
		for (WhereClauseWrapper where : requiredWhereClauses) {
			if (first) {
				whereHql.append(where.getWhereClause());
			} else {
				whereHql.append(" AND ").append(where.getWhereClause());
			}
		}
		// vamo adiconar a clausula where na hql
		if (whereHql.length() > 0) {
			whereHql.insert(0, "WHERE ");
			hql.append(whereHql);
		}
		_log.info(hql);
		Query query = em.createQuery(hql.toString().replaceAll(
				"(F|f)(E|e)(T|t)(C|c)(H|h)", ""));
		// vamos colocar os parametros
		for (ArgumentWrapper arg : requiredArguments) {
			if (arg.getArgument().operand() == Operand.IS_NOT_NULL
					|| arg.getArgument().operand() == Operand.IS_NULL) {
				continue;
			}
			query.setParameter(arg.getId(), arg.getValue());
		}
		return query;
	}
}